package ue.networking;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;

public class AccountSystem {
	
	private ArrayList<Account> accounts;
	
	public AccountSystem(String path) {
		this();
		loadFromXML(path);
	}
	
	public AccountSystem() {
		accounts = new ArrayList<Account>();
	}
	
	private void loadFromXML(String path) {
		Document doc = null;
		try {
			doc = new SAXBuilder().build(path);
		} catch (JDOMException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		Element root = doc.getRootElement();
		
		if(root.getName() != "accountsystem") {
			System.err.println("ERROR: Incorrect XML File: Wrong root name");
			System.exit(1);
		}
		
		int size = Integer.parseInt(root.getAttributeValue("size"));
		
		BigInteger p = new BigInteger(root.getAttributeValue("p"));
//		BigInteger g = new BigInteger(root.getAttributeValue("g"));
		
		BigInteger a = new BigInteger(p.bitLength(), new Random(1701)).mod(p);
//		System.out.println("a: " + a);
		
		BigInteger bt = new BigInteger(root.getAttributeValue("a"));

		SecretKeySpec key = Cryptography.generateAESKey(Cryptography.calculateDHKey(bt, a, p));
//		System.out.println("key: " + Cryptography.calculateDHKey(bt, a, p));
		
		List<?> children = root.getChildren();
		
		for(int i = 0; i < size; i++) {
			addDecryptAccount(key, ((Element)children.get(i)).getAttributeValue("name"), 
					((Element)children.get(i)).getAttributeValue("password"));
		}
	}

	public void saveToXML(String path, String filename) {
		Document doc = new Document();
		
		Element accountSystem = new Element("accountsystem");
		accountSystem.setAttribute(new Attribute("size", "" + accounts.size()));
		
		BigInteger p = Cryptography.generateDHPublicP(512, 128);
		BigInteger g = Cryptography.generateDHPublicG(p);
		
		BigInteger a = new BigInteger(p.bitLength(), new Random(1701)).mod(p);
		BigInteger b = Cryptography.generateDHPrivateNumber(p);
		
		BigInteger at = Cryptography.calculateDHTransferNumber(a, p, g);
		BigInteger bt = Cryptography.calculateDHTransferNumber(b, p, g);
		
		SecretKeySpec key = Cryptography.generateAESKey(Cryptography.calculateDHKey(at, b, p));
//		System.out.println("a: " + a);
//		System.out.println("key: " + Cryptography.calculateDHKey(at, b, p));
		
		accountSystem.setAttribute(new Attribute("p", p.toString()));
//		accountSystem.setAttribute(new Attribute("g", g.toString()));
		accountSystem.setAttribute(new Attribute("a", bt.toString()));
		
		for(int i = 0; i < accounts.size(); i++) {
			accountSystem.addContent(accounts.get(i).toXMLElement(key));
		}
		
		doc.addContent(accountSystem);
		
		File file = new File(path);
		file.mkdir();
		file = new File(path +  "/" + filename);
		try {
			file.createNewFile();
		} catch (IOException e2) {
			e2.printStackTrace();
		}
		
		try {
			PrintStream stream = new PrintStream(file);
			
			XMLOutputter out = new XMLOutputter(Format.getPrettyFormat());
			try {
				out.output(doc, stream);
			} catch (IOException e) {
				e.printStackTrace();
			}
			stream.close();
		} catch (FileNotFoundException e1) {
			e1.printStackTrace();
		}
	}
	
	public void addAccount(String name, String password) {
		accounts.add(new Account(name,password));
	}
	
	private void addDecryptAccount(SecretKeySpec key, String nameEncrypted, String passwordEncrypted) {
//		System.out.println(nameEncrypted + " : " + passwordEncrypted);
//		System.out.println(Arrays.toString(Base64.decodeBase64(nameEncrypted.getBytes())) + " : " + Base64.decodeBase64(passwordEncrypted.getBytes()).length);
		
		String name = new String(Cryptography.decodeAES(key, Base64.decodeBase64(nameEncrypted.getBytes())));
		String password = new String(Cryptography.decodeAES(key, Base64.decodeBase64(passwordEncrypted.getBytes())));
		
		addAccount(name, password);
	}
	
	public int changeAccountName(String oldName, String newName) {
		int i = findAccount(oldName);
		if(i >= 0 && i < accounts.size()) {
			accounts.get(i).setName(newName);
		}
		return i;
	}
	
	public int changeAccountPassword(String name, String password) {
		int i = findAccount(name);
		if(i >= 0 && i < accounts.size()) {
			accounts.get(i).setPassword(password);
		}
		return i;
	}
	
	private int findAccount(String name) {
		for(int i = 0; i < accounts.size(); i++) {
			if(accounts.get(i).getName().equals(name))
				return i;
		}
		return -1;
	}
	
	public int verifyAccount(String name, String password) {
		for(int i = 0; i < accounts.size(); i++) {
			if(name.equals(accounts.get(i).getName()) && 
					password.equals(accounts.get(i).getPassword())) {
				return i;
			}
		}
		return -1;
	}
	
	@Override
	public String toString() {
		String tmp = "AccountSystem: ";
		for(int i = 0; i < accounts.size(); i++) {
			tmp += accounts.get(i).toString() + ";";
		}
		return tmp;
	}
	
	private class Account {
		private String name;
		private String password;
		
		public Account(String name, String password) {
			this.name = name;
			this.password = password;
		}
		
		public Element toXMLElement(SecretKeySpec key) {
			Element element = new Element("account");
			element.setAttribute(new Attribute("name", Base64.encodeBase64String(Cryptography.encodeAES(key, name.getBytes()))));
			element.setAttribute(new Attribute("password", Base64.encodeBase64String(Cryptography.encodeAES(key, password.getBytes()))));
			
//			System.out.println(name.getBytes().length + " : " + password.getBytes().length);
//			System.out.println(Arrays.toString(Cryptography.encodeAES(key, name.getBytes())));
			
			return element;
		}

		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
		public String getPassword() {
			return password;
		}
		public void setPassword(String password) {
			this.password = password;
		}
		
		@Override
		public String toString() {
			return "Account[" + name + "|" + password + "]";
		}
	}
}
